package erfgame.core.j3d;

import java.awt.BorderLayout;
import java.awt.Frame;
import java.awt.GraphicsConfiguration;
import java.awt.Label;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.zip.GZIPOutputStream;

import javax.media.j3d.Canvas3D;
import javax.media.j3d.ImageComponent2D;
import javax.media.j3d.Screen3D;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.PosixParser;

import com.sun.j3d.loaders.Loader;
import com.sun.j3d.loaders.objectfile.ObjectFile;
import com.sun.j3d.utils.universe.SimpleUniverse;

import erfgame.core.Dimensions3D;
import erfgame.core.OperationProgress;
import erfgame.core.OperationProgressEvent;
import erfgame.core.OperationProgressListener;
import erfgame.core.Rectangle3D;
import erfgame.core.world.terrain.TerrainUpdate;
import erfgame.core.world.terrain.TerrainUpdateGroup;
import erfgame.core.world.terrain.factory.j3d.Shape3DTerrainUpdateByMaterialTypeBoundsAndRotationFactory;
import erfgame.core.world.terrain.file.TerrainUpdateSerializer;

public class ModelConverterMain {
	
	public static final String OPTION_WIDTH = "w";
	public static final String OPTION_HEIGHT = "h";
	public static final String OPTION_DEPTH = "d";
	
	public static final String OPTION_CHUNK_WIDTH = "W";
	public static final String OPTION_CHUNK_HEIGHT = "H";
	
	public static final String OPTION_IN_FILE = "f";
	public static final String OPTION_OUT_FILE = "o";
	public static final String OPTION_MATERIAL = "m";
	public static final String OPTION_PROPERTIES = "p";
	public static final String OPTION_ROTATION = "r";
	public static final String OPTION_LOADER = "l";
	public static final String OPTION_MERGE = "g";
	
	public static final String DEFAULT_WIDTH = "100";
	public static final String DEFAULT_HEIGHT = "100";
	public static final String DEFAULT_DEPTH = "100";
	
	public static final String DEFAULT_CHUNK_WIDTH = "128";
	public static final String DEFAULT_CHUNK_HEIGHT = "128";
	
	public static final String DEFAULT_ROTATION = "0";
	public static final String DEFAULT_LOADER = ObjectFile.class.getName();
	
	public static final Options createOptions() {
		Options options = new Options();
		options.addOption(createOption( OPTION_WIDTH, "width", true, "the maximum width of the target update", true ) );
		options.addOption(createOption( OPTION_HEIGHT, "height", true, "the maximum height of the target update", true ) );
		options.addOption(createOption( OPTION_DEPTH, "depth", true, "the maximum depth of the target update", true ) );
		options.addOption(createOption( OPTION_CHUNK_WIDTH, "chunk_width", true, "the maximum chunk width of the target update", true ) );
		options.addOption(createOption( OPTION_CHUNK_HEIGHT, "chunk_height", true, "the maximum chunk height of the target update", true ) );
		options.addOption(createOption( OPTION_IN_FILE, "file", true, "the input file", false ) );
		options.addOption(createOption( OPTION_OUT_FILE, "out", true, "the output file", false ) );
		options.addOption(createOption( OPTION_MATERIAL, "material", true, "the material id to use for the terrain", false ) );
		options.addOption(createOption( OPTION_PROPERTIES, "properties", true, "the properties file to use for the terrain materials", true ) );
		options.addOption(createOption( OPTION_ROTATION, "rotation", true, "the rotation angle of the model in degrees", true ) );
		options.addOption(createOption( OPTION_LOADER, "loader", true, "the fully qualified class name of the loader to use", true ) );
		options.addOption(createOption( OPTION_MERGE, "merge", false, "the model with merge into the terrain instead of overwriting it", true ) );
		return options;
	}
	
	public static final Option createOption( String opt, String title, boolean hasArg, String description, boolean optional ) {
		Option result = new Option(
				opt, 
				title, 
				hasArg, 
				description
		);
		result.setOptionalArg(optional);
		return result;
	}
	
	public static final void main( String[] args ) throws Exception {
		try {
			Options options = createOptions();
			CommandLineParser parser = new PosixParser();
			CommandLine cmd = parser.parse( options, args );
			
			String widthString  = cmd.getOptionValue(OPTION_WIDTH, DEFAULT_WIDTH);
			String heightString = cmd.getOptionValue(OPTION_HEIGHT, DEFAULT_HEIGHT);
			String depthString = cmd.getOptionValue(OPTION_DEPTH, DEFAULT_DEPTH);
			
			String chunkWidthString = cmd.getOptionValue(OPTION_CHUNK_WIDTH, DEFAULT_CHUNK_WIDTH);
			String chunkHeightString = cmd.getOptionValue(OPTION_CHUNK_HEIGHT, DEFAULT_CHUNK_HEIGHT);
			
			String inFileString = cmd.getOptionValue(OPTION_IN_FILE);
			String outFileString = cmd.getOptionValue(OPTION_OUT_FILE);
			String materialString = cmd.getOptionValue(OPTION_MATERIAL);
			String propertiesFileString = cmd.getOptionValue(OPTION_PROPERTIES);
			String rotationString = cmd.getOptionValue(OPTION_ROTATION, DEFAULT_ROTATION);
			String loaderString = cmd.getOptionValue(OPTION_LOADER, DEFAULT_LOADER);
			boolean merge = cmd.hasOption(OPTION_MERGE);
			
			int width = Integer.parseInt( widthString );
			int height = Integer.parseInt( heightString );
			int depth = Integer.parseInt( depthString );
			
			int chunkWidth = Integer.parseInt( chunkWidthString );
			int chunkHeight = Integer.parseInt( chunkHeightString );
			
			File inFile = new File( inFileString );
			File outFile = new File( outFileString );
	
			int rotation = Integer.parseInt( rotationString );
			
			byte materialType = Byte.parseByte(materialString);
	
			if( !inFile.exists() ) {
				throw new FileNotFoundException(inFileString);
			}
			
			Loader loader = (Loader)Class.forName(loaderString).newInstance();
			
			LoaderBranchGroupFactory branchGroupFactory = new LoaderBranchGroupFactory( loader, inFile.toURI().toURL() );
			
			short flags = 0;
			if( merge ) {
				flags = (short)(flags | TerrainUpdate.FLAG_OVERWRITE_IF_AIR);
			}
			
			final Label label = new Label( "loading..." );
			final JProgressBar progressBar = new JProgressBar(JProgressBar.HORIZONTAL);
			progressBar.setStringPainted(true);
			
			Frame frame = new Frame("converting");
			Canvas3D canvas = new Canvas3D(SimpleUniverse.getPreferredConfiguration(), false);
			canvas.setSize( 400, 400 );
			frame.add(canvas, BorderLayout.CENTER);
			frame.add(label, BorderLayout.NORTH);
			frame.add(progressBar, BorderLayout.SOUTH);
			frame.pack();
			frame.setVisible(true);
	
			try {
				
				Shape3DTerrainUpdateByMaterialTypeBoundsAndRotationFactory terrainUpdateFactory;
				terrainUpdateFactory = new Shape3DTerrainUpdateByMaterialTypeBoundsAndRotationFactory(branchGroupFactory, flags, canvas);
				
				Dimensions3D bounds = new Dimensions3D( width, height, depth );
				
				double yRotation = (rotation * Math.PI) / 180D;
				
				label.setText("mapping "+inFile.getCanonicalPath());
				
				OperationProgressListener listener = new OperationProgressListener() {
					
					@Override
					public void progressUpdated(OperationProgressEvent e) {
						OperationProgress progress = e.getProgress();
						progressBar.setString(progress.getName());
						progressBar.setMaximum(progress.getNumberOfSteps()-1);
						progressBar.setValue(progress.getStep());
					}
				};
				
				TerrainUpdate update = terrainUpdateFactory.createTerrainUpdate(materialType, bounds, yRotation, listener);
				
				label.setText( "splitting" );
				
				TerrainUpdateGroup group = TerrainUpdateGroup.split(update, chunkWidth, chunkHeight);

				label.setText( "saving" );

				try {
					GZIPOutputStream fos = new GZIPOutputStream(new FileOutputStream( outFile ) );
					try {
						TerrainUpdateSerializer.serializeGroup(group, fos);
					} finally {
						fos.close();
					}
				} catch( Exception ex ) {
					throw new RuntimeException(ex);
				}
				label.setText( "done" );
				System.out.println( "converted" );
				
			} finally {
				frame.dispose();
			}
		} catch ( Exception ex ) {
			ex.printStackTrace();
			System.exit( 1 );
		} finally {
			System.exit( 0 );
		}
	}
}
